﻿using System;
using System.IO;
using System.Runtime.CompilerServices;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.Ex.Audio
{
    unsafe public partial class WaveHelper
    {
        /****************************************************************************************************
         * IMA-ADPCM 编码和解码算法简介
         * IMA-ADPCM 是Intel公司首先开发的是一种主要针对16bit采样波形数据的有损压缩算法, 压缩比为4:1.
         * 它与通常的DVI-ADPCM是同一算法。         * (对8bit数据压缩时是3.2:1,也有非标准的IMA-ADPCM压缩算法,
         * 可以达到5:1甚至更高的压缩比),4:1的压缩是目前使用最多的压缩方式。
         * 
　　     * ADPCM(Adaptive Differential Pulse Code Modulation 差分脉冲编码调制)主要是针对连续的波形数据的, 
　　     * 保存的是相临波形的变化情况, 以达到描述整个波形的目的。
　　     * 
　　     * 注:经实践验证,效果很差,杂音很大
　　     * 算法中必须用到两个一维数组，setptab[] 和index_adjust[]:
　　     * int index_adjust[8] = {-1,-1,-1,-1,2,4,6,8}; 
　　     * int steptab[89] = 
　　     * { 
　　     *     7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 
　　     *     19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 
　　     *     50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 
　　     *     130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 
　　     *     337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 
　　     *     876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 
　　     *     2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 
　　     *     5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 
　　     *     15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
　　     * }; 
　　     * 
　　     * private static void AdpcmEncode(short[] pcmData)
  　　   * {
  　　   *     //首先我们认为声音信号都是从零开始的,那么需要初始化两个变量
  　　   *     int index = 0, prev_sample = 0;
  　　   *     byte[] adpcm = new byte[pcmData.Length / 2];
  　　   *     int adpcmIndex = 0;
  　　   *     int fg, code;
  　　   *     for (int i = 0; i < pcmData.Length; i++)
  　　   *     {
  　　   *         short cur_sample = pcmData[i];         // 得到PCM中的当前采样数据
  　　   *         var diff = cur_sample - prev_sample;      // 计算出和上一个的增量
  　　   * 
  　　   *         if (diff < 0)
  　　   *         {
  　　   *             diff = -diff;
  　　   *             fg = 8;
  　　   *         }
  　　   *         else
  　　   *         {
  　　   *             fg = 0;                          // fg 保存的是符号位
  　　   *         }
  　　   * 
  　　   *         code = 4 * diff / steptab[index];
  　　   * 
  　　   *         if (code > 7)
  　　   *         {
  　　   *             code = 7;                 // 根据steptab[] 得到一个0~7 的值，它描述了采样振幅的变化量
  　　   *         }
  　　   * 
  　　   *         index += index_adjust[code];          // 根据声音强度调整下次取steptab 的序号，便于下次得到更精确的变化量的描述
  　　   * 
  　　   *         if (index < 0)
  　　   *         {
  　　   *             index = 0;               // 调整index的值
  　　   *         }
  　　   *         else if (index > 88)
  　　   *         {
  　　   *             index = 88;
  　　   *         }
  　　   * 
  　　   *         prev_sample = cur_sample;
  　　   *         adpcm[adpcmIndex] = (byte)(code | fg);//加上符号位保存起来
  　　   *         adpcmIndex++;
  　　   *     }
  　　   * }
  　　   * 
  　　   * private static void AdpcmDecode(byte[] adpcmData)
  　　   * {
  　　   *     short[] pcmData = new short[adpcmData.Length * 2];
  　　   *     int pcmDataIndex = 0;
  　　   *     int index = 0, cur_sample = 0;
  　　   *     int fg;
  　　   *     short pcmVal;
  　　   * 
  　　   *     for (int i = 0; i < adpcmData.Length; i++)
  　　   *     {
  　　   *         byte code = adpcmData[i];           // 得到下一个压缩样品Code 4bit
  　　   *         if ((code & 8) != 0)
  　　   *         {
  　　   *             fg = 1;
  　　   *         }
  　　   *         else
  　　   *         {
  　　   *             fg = 0;
  　　   *         }
  　　   * 
  　　   *         code &= 7;                      // 将code 分离为数据和符号
  　　   *         var diff = (steptab[index] * code) / 4 + steptab[index] / 8;   // 后面加的一项是为了减少误差
  　　   * 
  　　   *         if (fg == 1)
  　　   *         {
  　　   *             diff = -diff;
  　　   *         }
  　　   * 
  　　   *         cur_sample += diff;            // 计算出当前的波形数据
  　　   * 
  　　   *         if (cur_sample > short.MaxValue)
  　　   *         {
  　　   *             pcmVal = short.MaxValue;
  　　   *         }
  　　   *         else if (cur_sample < short.MinValue)
  　　   *         {
  　　   *             pcmVal = short.MinValue;
  　　   *         }
  　　   *         else
  　　   *         {
  　　   *             pcmVal = (short)cur_sample;
  　　   *         }
  　　   * 
  　　   *         pcmData[pcmDataIndex] = pcmVal;
  　　   *         pcmDataIndex++;
  　　   * 
  　　   *         index += index_adjust[code];
  　　   *         if (index < 0)
  　　   *         {
  　　   *             index = 0;
  　　   *         }
  　　   *         else if (index > 88)
  　　   *         {
  　　   *             index = 88;
  　　   *         }
  　　   * 
  　　   *     }
  　　   * }
         ****************************************************************************************************/


        #region Adpcm转pcm

        /// <summary>
        /// Adpcm数据文件转换为pcm数据文件
        /// </summary>
        /// <param name="adpcmFilePath">带头的Adpcm数据文件路径</param>
        /// <param name="pcmFilePath">pcm数据文件存储路径</param>
        public static void AdpcmToPcm(string adpcmFilePath, string pcmFilePath)
        {
            if (string.IsNullOrEmpty(adpcmFilePath))
            {
                throw new ArgumentNullException(nameof(adpcmFilePath));
            }

            if (!File.Exists(adpcmFilePath))
            {
                throw new FileNotFoundException("带头的Adpcm数据文件路径", adpcmFilePath);
            }

            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(adpcmFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.Adpcm);
                var srcDataPtr = new UnmanagedMemoryPointer<byte>(srcFileStream.Length - srcFileWaveHeadSize);
                disposabler.Add(srcDataPtr, 0);

                var waveInfo = GetWavHead(srcFileStream);
                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.GetSpan());

                var destDataPtr = new UnmanagedMemoryPointer<short>(srcDataPtr.Length * 2);
                disposabler.Add(destDataPtr, 0);
                PrimitiveAdpcmToPcm(srcDataPtr.DataPtr, destDataPtr.DataPtr, srcDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(pcmFilePath);
                var destFileStream = new FileStream(pcmFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.Pcm);
                destFileStream.Write(destDataPtr.Cast<byte>().ToSpan());

                FillPcmWavHead(destFileStream, WaveEncoding.Pcm, DataSampleBits.BIT16, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)(destDataPtr.Length * sizeof(short)));
            }
        }

        /// <summary>
        /// Adpcm数据转换为pcm数据
        /// </summary>
        /// <param name="adpcmData">Adpcm数据</param>
        /// <returns>pcm数据</returns>
        public static short[] AdpcmToPcm(byte[] adpcmData)
        {
            if (adpcmData == null)
            {
                throw new ArgumentNullException(nameof(adpcmData));
            }

            short[] pcmData = new short[adpcmData.Length * 2];

            fixed (byte* adpcmDataPtr = adpcmData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveAdpcmToPcm(adpcmDataPtr, pcmDataPtr, adpcmData.Length);
                }
            }

            return pcmData;
        }

        /// <summary>
        /// Adpcm数据转换为pcm数据
        /// </summary>
        /// <param name="adpcmData">Adpcm数据</param>
        /// <param name="adpcmDataIndex">Adpcm数据起始索引</param>
        /// <param name="pcmData">pcm数据(数据长度为adpcm数据长度的2倍)</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="length">转换Adpcm数据长度</param>
        public static void AdpcmToPcm(byte[] adpcmData, int adpcmDataIndex, short[] pcmData, int pcmDataIndex, int length)
        {
            if (adpcmData == null)
            {
                throw new ArgumentNullException(nameof(adpcmData));
            }

            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (adpcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(adpcmDataIndex), "Adpcm数据起始索引不能小于0");
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (adpcmDataIndex + length > adpcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"Adpcm数据起始索引\"加\"转换数据长度\"大于了\"Adpcm数据\"长度");
            }

            if (pcmDataIndex + length * 2 > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (byte* adpcmDataPtr = adpcmData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveAdpcmToPcm(adpcmDataPtr + adpcmDataIndex, pcmDataPtr + pcmDataIndex, length);
                }
            }

        }

        /// <summary>
        /// Adpcm数据转换为pcm数据
        /// </summary>
        /// <param name="adpcmData">Adpcm数据</param>
        /// <param name="pcmData">pcm数据(数据长度为adpcm数据长度的2倍)</param>
        public static void AdpcmToPcm(Span<byte> adpcmData, Span<short> pcmData)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            byte* adpcmDataPtr = adpcmData.DataPtr;
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            byte* adpcmDataPtr = (byte*)Unsafe.AsPointer<byte>(ref adpcmData[0]);
#endif
            PrimitiveAdpcmToPcm(adpcmDataPtr, pcmDataPtr, Math.Min(adpcmData.Length, pcmData.Length / 2));
        }

        /// <summary>
        /// Adpcm数据转换为pcm数据
        /// </summary>
        /// <param name="adpcmData">Adpcm数据</param>
        /// <param name="pcmData">pcm数据(数据长度为adpcm数据长度的2倍)</param>
        public static void AdpcmToPcm(SpanZ<byte> adpcmData, SpanZ<short> pcmData)
        {
            PrimitiveAdpcmToPcm(adpcmData.DataPtr, pcmData.DataPtr, Math.Min(adpcmData.Length, pcmData.Length / 2));
        }

        private static void PrimitiveAdpcmToPcm(byte* adpcmDataPtr, short* pcmDataPtr, int adpcmDataLength)
        {
            CheckAdpcmCodeTable();
            short valprev = *((short*)adpcmDataPtr);
            int index = adpcmDataPtr[2];

            int pcmIndex = 0;
            for (int i = 4; i < adpcmDataLength; i++)
            {
                byte adpcmByte = adpcmDataPtr[i];
                int nibble1 = adpcmByte & 0x0F;
                int nibble2 = (adpcmByte >> 4) & 0x0F;

                pcmDataPtr[pcmIndex++] = AdpcmDecodeNibble(nibble1, ref valprev, ref index);
                pcmDataPtr[pcmIndex++] = AdpcmDecodeNibble(nibble2, ref valprev, ref index);
            }

        }

        private static short AdpcmDecodeNibble(int nibble, ref short valprev, ref int index)
        {
            int step = _adpcmStepSizeTable[index];
            int delta = step >> 3;

            if ((nibble & 4) != 0) delta += step;
            if ((nibble & 2) != 0) delta += step >> 1;
            if ((nibble & 1) != 0) delta += step >> 2;

            if ((nibble & 8) != 0) delta = -delta;

            int value = valprev + delta;
            if (value > 32767) value = 32767;
            else if (value < -32768) value = -32768;

            valprev = (short)value;
            index += _adpcmIndexTable[nibble];
            if (index < 0)
            {
                index = 0;
            }
            else if (index > 88)
            {
                index = 88;
            }

            return valprev;
        }

        #endregion

        // ADPCM编解码表
        private static short[] _adpcmIndexTable = null;
        private static short[] _adpcmStepSizeTable = null;
        private static void CheckAdpcmCodeTable()
        {
            if (_adpcmIndexTable != null)
            {
                return;
            }

            _adpcmIndexTable = new short[]
            {
                -1, -1, -1, -1, 2, 4, 6, 8,
                -1, -1, -1, -1, 2, 4, 6, 8
            };

            _adpcmStepSizeTable = new short[]
            {
                7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
                19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
                50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
                130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
                337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
                876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
                2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
                5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
                15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
            };
        }

        #region pcm转Adpcm

        /// <summary>
        /// pcm数据文件转换为adpcm数据文件
        /// </summary>
        /// <param name="pcmFilePath">带头的pcm数据文件路径</param>
        /// <param name="adpcmFilePath">adpcm数据文件存储路径</param>
        public static void PcmToAdpcm(string pcmFilePath, string adpcmFilePath)
        {
            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            if (!File.Exists(pcmFilePath))
            {
                throw new FileNotFoundException("带头的pcm数据文件路径不存在", pcmFilePath);
            }

            if (string.IsNullOrEmpty(adpcmFilePath))
            {
                throw new ArgumentNullException(nameof(adpcmFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(pcmFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                var waveInfo = GetWavHead(srcFileStream);
                if (waveInfo.SampleBits != 16)
                {
                    throw new InvalidOperationException($"仅支持16位采样位数的pcm数据文件转换为Adpcm数据文件,当前数据采样位数为{waveInfo.SampleBits}");
                }

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.Pcm);
                var srcDataPtr = new UnmanagedMemoryPointer<short>((srcFileStream.Length - srcFileWaveHeadSize) / sizeof(short));
                disposabler.Add(srcDataPtr, 0);

                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.Cast<byte>().ToSpan());

                var destDataPtr = new UnmanagedMemoryPointer<byte>((srcDataPtr.Length + 1) / 2);
                disposabler.Add(destDataPtr, 0);
                PrimitivePcmToAdpcm(srcDataPtr.DataPtr, destDataPtr.DataPtr, srcDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(adpcmFilePath);
                var destFileStream = new FileStream(adpcmFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.Adpcm);
                destFileStream.Write(destDataPtr.GetSpan());

                FillAdpcmWavHead(destFileStream, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)destDataPtr.Length);
            }
        }

        /// <summary>
        /// pcm数据转换为adpcm数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <returns>adpcm数据</returns>
        public static byte[] PcmToAdpcm(short[] pcmData)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            byte[] adpcmData = new byte[(pcmData.Length + 1) / 2];
            fixed (byte* adpcmDataPtr = adpcmData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToAdpcm(pcmDataPtr, adpcmDataPtr, pcmData.Length);
                }
            }
            return adpcmData;
        }

        /// <summary>
        /// pcm数据转换为adpcm数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="adpcmData">adpcm数据</param>
        /// <param name="adpcmDataIndex">adpcm数据起始索引</param>
        /// <param name="length">转换数据长度</param>
        public static void PcmToAdpcm(short[] pcmData, int pcmDataIndex, byte[] adpcmData, int adpcmDataIndex, int length)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (adpcmData == null)
            {
                throw new ArgumentNullException(nameof(adpcmData));
            }

            if (adpcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(adpcmDataIndex), "Adpcm数据起始索引不能小于0");
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (adpcmDataIndex + (length + 1) / 2 > adpcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"Adpcm数据起始索引\"加\"转换数据长度\"大于了\"Adpcm数据\"长度");
            }

            if (pcmDataIndex + length > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (byte* adpcmDataPtr = adpcmData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToAdpcm(pcmDataPtr + pcmDataIndex, adpcmDataPtr + adpcmDataIndex, length);
                }
            }
        }

        /// <summary>
        /// pcm数据转换为adpcm数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="adpcmData">adpcm数据</param>
        public static void PcmToAdpcm(Span<short> pcmData, Span<byte> adpcmData)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            byte* adpcmDataPtr = adpcmData.DataPtr;
            
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            byte* adpcmDataPtr = (byte*)Unsafe.AsPointer<byte>(ref adpcmData[0]);
#endif

            PrimitivePcmToAdpcm(pcmDataPtr, adpcmDataPtr, Math.Min((pcmData.Length + 1) / 2, adpcmData.Length));
        }

        /// <summary>
        /// pcm数据转换为adpcm数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="adpcmData">adpcm数据</param>
        public static void PcmToAdpcm(SpanZ<short> pcmData, SpanZ<byte> adpcmData)
        {
            PrimitivePcmToAdpcm(pcmData.DataPtr, adpcmData.DataPtr, Math.Min((pcmData.Length + 1) / 2, adpcmData.Length));
        }

        private static void PrimitivePcmToAdpcm(short* pcmDataPtr, byte* adpcmDataPtr, int pcmDataLength)
        {
            CheckAdpcmCodeTable();

            short valprev = 0, pcmSample;
            int index = 0;

            int adpcmIndex = 0;
            byte currentByte = 0;
            int nibble;

            for (int i = 0; i < pcmDataLength; i++)
            {
                pcmSample = pcmDataPtr[i];
                nibble = AdpcmEncodeNibble(pcmSample, ref valprev, ref index);

                if (i % 2 == 0)
                {
                    currentByte = (byte)(nibble & 0x0F);
                }
                else
                {
                    currentByte |= (byte)((nibble << 4) & 0xF0);
                    adpcmDataPtr[adpcmIndex++] = currentByte;
                }
            }

            // 如果 PCM 数据长度为奇数，需要单独处理最后一个半字节
            if (pcmDataLength % 2 == 1)
            {
                adpcmDataPtr[adpcmIndex] = (byte)(currentByte & 0xF0);
            }
        }

        private static int AdpcmEncodeNibble(short pcmSample, ref short valprev, ref int index)
        {
            int step = _adpcmStepSizeTable[index];
            int diff = pcmSample - valprev;
            int nibble = 0;

            if (diff < 0)
            {
                nibble |= 0x8;
                diff = -diff;
            }

            int delta = step >> 3;
            if (diff >= step)
            {
                nibble |= 0x4;
                diff -= step;
                delta += step;
            }

            step >>= 1;
            if (diff >= step)
            {
                nibble |= 0x2;
                diff -= step;
                delta += step;
            }

            step >>= 1;
            if (diff >= step)
            {
                nibble |= 0x1;
                delta += step;
            }

            valprev += (short)(delta * (nibble & 0x8) == 0 ? 1 : -1);
            index += _adpcmIndexTable[nibble];
            if (index < 0)
            {
                index = 0;
            }
            else if (index > 88)
            {
                index = 88;
            }

            return nibble;
        }

        #endregion

    }
}
